
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <math.h>
#include <net/if.h>
#include <netdb.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>

#include "daq.h"
#include "libaxidma.h"
#include "quicklz.h"

extern void server_thread();

// reg operate
int g_iAn706MemFileDev;
void *g_pAn706RegBase;

// axidma operate
axidma_dev_t g_AxiDmaDev;
const array_t *g_apRxChannels;
void *g_pAn706DataBufBase;
uint16_t *g_pAdcBuff[2];

uint8_t which_buff = 0;
uint8_t g_waitFlag = false;
pthread_t thread[2];
sem_t recv_sem;

int led_fd[4];

// tcp
static int socket_fd = -1;
static int quit = 0;
static const size_t MAX_FRAME_SIZE = 4100;
static char *tcp_server_ip = "192.168.10.58";
static int tcp_server_port = 50000;
int net_status = 0;

void adc_start(int num) {
  if (g_waitFlag) {
    printf("adc is running!");
    return;
  }
  printf("start adc and dma,len = %d\n", num);
  axidma_oneway_transfer(g_AxiDmaDev, g_apRxChannels->data[0], g_pAdcBuff[0],
                         num * 2, false);
  *(volatile unsigned long *)(g_pAn706RegBase) = 1;  // start
  g_waitFlag = true;
}

int ledlist[] = {1019, 1020, 1021, 1022};

void led_init() {
  for (uint32_t i = 0; i < sizeof(ledlist) / sizeof(ledlist[0]); i++) {
    // echo 9 > /sys/class/gpio/export
    int led_fd = open("/sys/class/gpio/export", O_WRONLY);
    if (led_fd < 0) {
      printf("open /sys/class/gpio/export failed \n");
      continue;
    }
    char ids[10] = {0};
    sprintf(ids, "%d", ledlist[i]);

    write(led_fd, ids, strlen(ids));
    close(led_fd);

    char cmd[64] = {0};
    sprintf(cmd, "/sys/class/gpio/gpio%d/direction", ledlist[i]);
    // echo out > /sys/class/gpio/gpio9/direction
    led_fd = open(cmd, O_WRONLY);
    if (led_fd < 0) {
      printf("open %s failed\n", cmd);
      continue;
    }
    write(led_fd, "out", strlen("out"));
    close(led_fd);
  }
}

void set_led(int id, int stat) {
  if (id > 4) {
    return;
  }

  char cmd[64] = {0};
  sprintf(cmd, "/sys/class/gpio/gpio%d/value", ledlist[id - 1]);

  int fd = open(cmd, O_WRONLY);
  if (fd < 0) {
    return;
  }

  if (stat) {
    write(fd, "1", 1);
  } else {
    write(fd, "0", 1);
  }
  close(fd);
}

/**
 * @brief  配置ADC
 * @note
 * @retval None
 */
void adc_init() {
  uint16_t adc_reg_list[] = {
      // ch to sigleend 5v  0x11, dif 5v 0x88
      (0 << 15) | (0x03 << 8) | 0x11,
      (0 << 15) | (0x04 << 8) | 0x11,
      (0 << 15) | (0x05 << 8) | 0x11,
      (0 << 15) | (0x06 << 8) | 0x11,
      // BW HIGH
      (0 << 15) | (0x07 << 8) | 0xFF,
  };

  *(volatile unsigned long *)(g_pAn706RegBase) = 2;  // reset

  int delay = 500;
  while (delay--)
    ;
  for (unsigned int i = 0; i < sizeof(adc_reg_list) / sizeof(adc_reg_list[0]);
       i++) {
    *(volatile unsigned long *)(g_pAn706RegBase + 12) = adc_reg_list[i];
    // cfg reg
    delay = 20;
    while (delay--)
      ;
    *(volatile unsigned long *)(g_pAn706RegBase) = 1 << 4;  // control ,write
    delay = 20;
    while (delay--)
      ;
  }
  // back to ad mode
  *(volatile unsigned long *)(g_pAn706RegBase + 12) = 0;  // cfg reg
  delay = 20;
  while (delay--)
    ;
  *(volatile unsigned long *)(g_pAn706RegBase) = 1 << 4;  // control ,write cfg
  delay = 20;
  while (delay--)
    ;

  // config dut
  *(volatile unsigned long *)(g_pAn706RegBase + 8) =
      (NO_STAND_BY | (5 << 2) | ((8 - 1) << 5));  // dut reg
  delay = 20;
  while (delay--)
    ;
  *(volatile unsigned long *)(g_pAn706RegBase) = 1 << 3;  // control ,write dut
  delay = 200;
  while (delay--)
    ;
}

void adc_write_reg(int val) {
  // config dut
  *(volatile unsigned long *)(g_pAn706RegBase + 8) = val;  // dut reg
  int delay = 20;
  while (delay--)
    ;
  *(volatile unsigned long *)(g_pAn706RegBase) = 1 << 3;  // control ,write dut
  delay = 200;
  while (delay--)
    ;
}

void adc_set_num(int num) {
  *(volatile unsigned long *)(g_pAn706RegBase + 4) = num;
}

time_t get_ms() {
  struct timeval tv;

  gettimeofday(&tv, NULL);
  return (tv.tv_sec * 1000 + tv.tv_usec / 1000);
}

int compress(uint8_t *data, int len) {
  int destinationSize = len * 1.5;
  time_t startM = get_ms();
  char *destination = (char *)malloc(destinationSize);
  memset(destination, 0x00, destinationSize);

  qlz_state_compress *state_compress =
      (qlz_state_compress *)malloc(sizeof(qlz_state_compress));
  memset(state_compress, 0x00, sizeof(qlz_state_compress));

  // int compressedSize = qlz_compress((char *)source, (char *)destination,
  // dataLength, state_compress);
  time_t startC = get_ms();
  int cmprs_size = qlz_compress(data, (char *)destination, len, state_compress);
  printf("compress ratio:%.1f\n", (float)cmprs_size / len);
  printf("compress len:%d, cost:%ld ms, %ld\n", len, get_ms() - startC,
         get_ms() - startM);
  free(destination);
  return cmprs_size;
}

void callbackAfterRecive(int channelid, void *data) {
  (void)(data);
  (void)(channelid);

  // sem_post(&recv_sem);

  g_waitFlag = false;
  printf("INFO: callback func triggerd,channelid: %d\n", channelid);
  for (int i = 0; i < 20; i++) {
    printf("%x ", *((unsigned short *)(g_pAn706DataBufBase) + i));
  }
  printf("\n");

  printf("dma over\n");
}

void *adc_thread(void *par) {
  (void)par;
  uint8_t blink = 0;
  printf("adc_thread : start\n");
  while (1) {
    daq_process();
    sleep(1);
    blink = 1 - blink;
    set_led(4, blink);
  }
  printf("adc_thread : stop\n");
  pthread_exit(NULL);
  return 0;
}

static void tcp_client_handle_sighup(int sig) {
  // TODO：do something while recv sighup
}

static int8_t client_connet_to_server() {
  struct sigaction sa;
  struct sockaddr_in clientAddr;
  struct sockaddr_in serverAddr;

  if (socket_fd != -1) {
    shutdown(socket_fd, SHUT_RDWR);
    close(socket_fd);
    socket_fd = -1;
  }

  memset(&sa, 0, sizeof(struct sigaction));
  sa.sa_handler = SIG_IGN;
  sigemptyset(&sa.sa_mask);
  sa.sa_flags = 0;
  if (sigaction(SIGPIPE, &sa, NULL) == -1) {
    fprintf(stderr, "sigaction() Failed: %d", errno);
    return -1;
  }

  bzero(&clientAddr, sizeof(clientAddr));
  clientAddr.sin_family = AF_INET;
  clientAddr.sin_addr.s_addr = htons(INADDR_ANY);
  clientAddr.sin_port = htons(0);

  //创建client
  socket_fd = socket(AF_INET, SOCK_STREAM, 0);
  if (socket_fd < 0) {
    fprintf(stderr, "initial tcp_client create socket failed\n");
    return -1;
  }

  int ret;

  /* Disable the Nagle (TCP No Delay) algorithm */
  int flag = 1;

  // 将socket建立为非阻塞，此时socket被设置为非阻塞模式
  // flag = fcntl(socket_fd, F_GETFL, 0); //获取建立的sockfd的当前状态（非阻塞）
  // fcntl(socket_fd, F_SETFL, flag | O_NONBLOCK);  //将当前sockfd设置为非阻塞

  if (bind(socket_fd, (struct sockaddr *)&clientAddr, sizeof(clientAddr)) ==
      -1) {
    fprintf(stderr, "initial tcp_client bind socket failed\n");
    return -1;
  }

  bzero(&serverAddr, sizeof(serverAddr));
  serverAddr.sin_family = AF_INET;
  serverAddr.sin_addr.s_addr = inet_addr(tcp_server_ip);
  serverAddr.sin_port = htons(tcp_server_port);

  while (connect(socket_fd, (struct sockaddr *)&serverAddr,
                 sizeof(serverAddr)) == -1) {
    static int8_t circleTime = 0;
    ++circleTime;
    if (circleTime >= 20) {
      circleTime = 0;
    }
    if (quit) {
      fprintf(stderr, "tcp client thread terminated\n");
      return -1;
    }

    sleep(2);
    fprintf(stderr, "start to connect the host:%s\n", tcp_server_ip);
  }

  // ret = setsockopt( socket_fd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag,
  // sizeof(flag) ); if (ret == -1) {
  //   printf("Couldn't setsockopt(TCP_NODELAY)\n");
  //   exit(-1);
  // }
  int sock_buf_size = 80 * 1024 * 1024;
  ret = setsockopt(socket_fd, SOL_SOCKET, SO_SNDBUF, (char *)&sock_buf_size,
                   sizeof(sock_buf_size));
  ret = setsockopt(socket_fd, SOL_SOCKET, SO_RCVBUF, (char *)&sock_buf_size,
                   sizeof(sock_buf_size));

  fprintf(stderr, "connect the host:%s success\n", tcp_server_ip);
  return 0;
}

// send to server
void send_data(uint8_t *data, uint16_t len) {
  // printf("send_data\n");
  if (socket_fd > 0 && net_status) {
    if (write(socket_fd, (const void *)data, len) <= 0) {
      net_status = 0;
    }
  }
}

// connect success
void on_server_connect() {
  net_status = 1;
  // send a fuck pack to clear remote buffer
  uint8_t buff[1000] = {0};
  for (int i = 0; i < 50; i++) {
    send_data(buff, 1000);
  }
}

/**
 *  tcp thread
 */
void *tcp_thread(void *par) {
  char *buf = "client test";
  char recv_buf[MAX_FRAME_SIZE];
  int recv_len = 0;
  int i;
  fd_set fdset;

  while (1) {
    if (client_connet_to_server() == 0) {
      on_server_connect();

      while (1) {
        FD_ZERO(&fdset);
        FD_SET(socket_fd, &fdset);
        struct timeval timeout = {5, 0};  // 5妙超时时间
        int n = select(socket_fd + 1, &fdset, NULL, NULL, &timeout);
        if (n > 0) {
          if (FD_ISSET(socket_fd, &fdset)) {
            int count = recv(socket_fd, recv_buf, MAX_FRAME_SIZE, 0);

            if (count > 0) {
              DRV_Unpack(&net_packer, recv_buf, count);
              fprintf(stderr, "/***********tcp recv:%d/\n", count);
            } else if (n == 1) {
              printf("recv ret: %d n=%d\n", recv_len, n);
              net_status = 0;
              break;
            }
          }
        } else if (n == 0) {
          // printf("recv timeout\n");
          if (net_status == 0) {
            break;
          }
        } else {
          break;
        }
      }
      printf("sock is broken\n");
      close(socket_fd);
    }
    sleep(1);
  }
}

void init_thread() {
  int temp;
  sem_init(&recv_sem, 0, 0);

  memset(&thread, 0, sizeof(thread));  // comment1
  //创建线程
  if ((temp = pthread_create(&thread[0], NULL, adc_thread, NULL)) != 0) {
    printf("ADC线程创建失败!\n");
  }

  //创建线程
  if ((temp = pthread_create(&thread[1], NULL, tcp_thread, NULL)) != 0) {
    printf("TCP线程创建失败!\n");
  }
}

void init_tcp_par() {
  const char *cmd1 = "/proc/sys/net/core/wmem_default";
  const char *cmd2 = "/proc/sys/net/core/wmem_max";

  int fd = open(cmd1, O_WRONLY);
  if (fd < 0) {
    return;
  }
  write(fd, "83886080", strlen("83886080"));
  close(fd);

  fd = open(cmd2, O_WRONLY);
  if (fd < 0) {
    return;
  }
  write(fd, "83886080", strlen("83886080"));
  close(fd);
}

int main(int argc, char *argv[]) {
  if (argc < 3) {
    printf("must input ip and port");
    return 0;
  }
  tcp_server_ip = argv[1];
  tcp_server_port = atoi(argv[2]);
  printf("set ip = %s and port = %d\n", tcp_server_ip, tcp_server_port);
  init_tcp_par();

  // init axidma device
  g_AxiDmaDev = axidma_init();
  if (g_AxiDmaDev == NULL) {
    printf("Error: can not initialize the AXI DMA device\n");
    return 0;
  }

  // get avaliable rx channels
  g_apRxChannels = axidma_get_dma_rx(g_AxiDmaDev);
  if (g_apRxChannels->len < 1) {
    printf("Error: no receive channels found\n");
    return 0;
  }

  // print avaliable channels
  for (int i = 0; i < g_apRxChannels->len; i++) {
    printf("INFO: receive channel ID: %d\n", g_apRxChannels->data[i]);
  }

  // mmap the an706 reg base
  g_iAn706MemFileDev = open("/dev/mem", O_RDWR | O_SYNC);
  if (g_iAn706MemFileDev < 0) {
    printf("ERROR: can not open /dev/mem\n");
    return 0;
  }
  g_pAn706RegBase = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED,
                         g_iAn706MemFileDev, AN706_REGBASE);
  if (!g_pAn706RegBase) {
    printf("ERROR: unable to mmap adc registers\n");

    // close an706 memory file dev
    if (g_iAn706MemFileDev) close(g_iAn706MemFileDev);

    return 0;
  }

  led_init();

  // start process thread
  init_thread();

  // start DMA recive
  g_pAn706DataBufBase = axidma_malloc(g_AxiDmaDev, AN706_SAMPLENUM * 2);  // x2
  g_pAdcBuff[0] = g_pAn706DataBufBase;

  printf("the init dma recive buf:\n");

  printf("preper to start adc\n");
  axidma_stop_transfer(g_AxiDmaDev, g_apRxChannels->data[0]);
  axidma_set_callback(g_AxiDmaDev, g_apRxChannels->data[0], callbackAfterRecive,
                      NULL);
  // axidma_oneway_transfer(g_AxiDmaDev, g_apRxChannels->data[0],
  // g_pAdcBuff[which_buff], AN706_SAMPLENUM * 2 ,  false);

  daq_init();

  // adc_set_num(10);
  // adc_start(10);

  printf("the ADC has start...\n");

  //等待线程结束
  if (thread[0] != 0) {
    pthread_join(thread[0], NULL);
    printf("process thread descroy\n");
  }

  sem_destroy(&recv_sem);

  // while (g_waitFlag)
  //     sleep(1);

  // close an706 memory file dev
  if (g_iAn706MemFileDev) close(g_iAn706MemFileDev);

  // munmap
  munmap(g_pAn706RegBase, MAP_SHARED);

  axidma_free(g_AxiDmaDev, g_pAn706DataBufBase, AN706_SAMPLENUM * 2);
  axidma_destroy(g_AxiDmaDev);

  return 0;
}
